﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;

namespace TaskBasedSchedule.Core
{
    public class Authorities
    {

        #region Fields

        private ReaderWriterLock _syncLocker;

        private Dictionary<string, AuthoritySlim> _keyAuthorityDic;
        private Dictionary<int, AuthoritySlim> _idAuthorityDic;

        #endregion

        #region Properties

        public string this[int id]
        {
            get
            {
                _syncLocker.AcquireReaderLock(Timeout.Infinite);

                string key = null;

                try
                {
                    if (_idAuthorityDic.ContainsKey(id))
                    {
                        key = _idAuthorityDic[id]._key;
                    }
                }
                finally
                {
                    _syncLocker.ReleaseReaderLock();
                }

                return key;
            }
        }

        public int this[string key]
        {
            get {

                _syncLocker.AcquireReaderLock(Timeout.Infinite);

                int identity = -1;

                try
                {
                    if (_keyAuthorityDic.ContainsKey(key))
                    {
                        identity = _keyAuthorityDic[key]._id;
                    }
                }
                finally
                {
                    _syncLocker.ReleaseReaderLock();
                }

                return identity;
            }
        }

        #endregion
        
        #region Constructor&Destructor

        internal Authorities()
        {
            PrivateInitialize();
        }

        #endregion
        
        #region Public Functions

        public void AddAuthority(string key,string title, Authority parent)
        {
            if (!Framework.Instance.CurrentContext.CheckAuthority(BasicAuthoritiesEnum.ModifyAuthorities))
            {
                //TODO: Limit Authority Exception.
                return;
            }

            if (parent == null || string.IsNullOrEmpty(key) || string.IsNullOrEmpty(title))
            {
                //TODO: Arguments invalid exception.
                return;
            }

            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            if (!_idAuthorityDic.ContainsKey(parent.Id))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Parent authority doesn't exist exception
                return;
            }

            if (_keyAuthorityDic.ContainsKey(key))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Target key has already existed.
                return;
            }

            //Acquire a write lock.
            _syncLocker.UpgradeToWriterLock(Timeout.Infinite);

            int id;
            try
            {
                switch (Framework.Instance.Database.WorkingDatabaseType)
                {
                    case DatabaseType.MsSql:
                        id = PrivateMsSqlAddAuthority(key, title, parent.Id);
                        break;
                    default:
                        _syncLocker.ReleaseWriterLock();
                        //TODO: Throw unsupport exception.
                        return;
                }
            }
            catch
            {
                //Release locker.
                _syncLocker.ReleaseWriterLock();
                //Just throw the DatabaseOperationException.
                throw;
            }

            AuthoritySlim auSlim = new AuthoritySlim();
            auSlim._id = id;
            auSlim._key = key;
            auSlim._parent = parent.Id;
            auSlim._title = title;

            _idAuthorityDic.Add(id, auSlim);
            _keyAuthorityDic.Add(key, auSlim);

            _syncLocker.ReleaseWriterLock();
        }

        public void AddAuthority(string key, string title, int parentId)
        {
            if (!Framework.Instance.CurrentContext.CheckAuthority(BasicAuthoritiesEnum.ModifyAuthorities))
            {
                //TODO: Limit Authority Exception.
                return;
            }

            if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(title))
            {
                //TODO: Arguments invalid exception.
                return;
            }

            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            if (!_idAuthorityDic.ContainsKey(parentId))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Parent authority doesn't exist exception
                return;
            }

            if (_keyAuthorityDic.ContainsKey(key))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Target key has already existed.
                return;
            }

            //Acquire a write lock.
            _syncLocker.UpgradeToWriterLock(Timeout.Infinite);

            int id;
            try
            {
                switch (Framework.Instance.Database.WorkingDatabaseType)
                {
                    case DatabaseType.MsSql:
                        id = PrivateMsSqlAddAuthority(key, title, parentId);
                        break;
                    default:
                        _syncLocker.ReleaseWriterLock();
                        //TODO: Throw unsupport exception.
                        return;
                }
            }
            catch
            {
                //Release locker.
                _syncLocker.ReleaseWriterLock();
                //Just throw the DatabaseOperationException.
                throw;
            }

            AuthoritySlim auSlim = new AuthoritySlim();
            auSlim._id = id;
            auSlim._key = key;
            auSlim._parent = parentId;
            auSlim._title = title;

            _idAuthorityDic.Add(id, auSlim);
            _keyAuthorityDic.Add(key, auSlim);

            _syncLocker.ReleaseWriterLock();
        }

        public void AddAuthority(string key, string title, string parentKey)
        {
            if (!Framework.Instance.CurrentContext.CheckAuthority(BasicAuthoritiesEnum.ModifyAuthorities))
            {
                //TODO: Limit Authority Exception.
                return;
            }

            if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(title) || string.IsNullOrEmpty(parentKey))
            {
                //TODO: Arguments invalid exception.
                return;
            }

            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            if (!_keyAuthorityDic.ContainsKey(parentKey))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Parent authority doesn't exist exception
                return;
            }

            if (_keyAuthorityDic.ContainsKey(key))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Target key has already existed.
                return;
            }

            //Acquire a write lock.
            _syncLocker.UpgradeToWriterLock(Timeout.Infinite);

            int id;
            try
            {
                switch (Framework.Instance.Database.WorkingDatabaseType)
                {
                    case DatabaseType.MsSql:
                        id = PrivateMsSqlAddAuthority(key, title, _keyAuthorityDic[parentKey]._id);
                        break;
                    default:
                        _syncLocker.ReleaseWriterLock();
                        //TODO: Throw unsupport exception.
                        return;
                }
            }
            catch
            {
                //Release locker.
                _syncLocker.ReleaseWriterLock();
                //Just throw the DatabaseOperationException.
                throw;
            }

            AuthoritySlim auSlim = new AuthoritySlim();
            auSlim._id = id;
            auSlim._key = key;
            auSlim._parent = _keyAuthorityDic[parentKey]._id;
            auSlim._title = title;

            _idAuthorityDic.Add(id, auSlim);
            _keyAuthorityDic.Add(key, auSlim);

            _syncLocker.ReleaseWriterLock();
        }

        public void ConfigAuthority(string configText)
        {
            _syncLocker.AcquireWriterLock(Timeout.Infinite);

            try
            {
                PrivateMsSqlConfigAuthorityBatch(configText);
            }
            finally
            {
                _syncLocker.ReleaseWriterLock();
            }
        }

        public void AlterTitle(Authority authority, string newTitle)
        {
            //TODO: Code here
            throw new System.NotImplementedException();
        }

        public void AlterTitle(int authorityId, string newTitle)
        {
            //TODO: Code here
            throw new System.NotImplementedException();
        }

        public void AlterTitle(string authorityKey, string newTitle)
        {
            //TODO: Code here
            throw new System.NotImplementedException();
        }

        public void AlterParent(Authority authority, Authority newParent)
        {
            //TODO: Code here
            throw new System.NotImplementedException();
        }

        public void AlterParent(int authorityId, int newParentId)
        {
            //TODO: Code here
            throw new System.NotImplementedException();
        }

        public void AlterParent(string authorityKey, string newParentKey)
        {
            if (!Framework.Instance.CurrentContext.CheckAuthority(BasicAuthoritiesEnum.ModifyAuthorities))
            {
                //TODO: Limit Authority Exception.
                return;
            }

            if (string.IsNullOrEmpty(authorityKey) || string.IsNullOrEmpty(newParentKey))
            {
                //TODO: Arguments invalid exception.
                return;
            }

            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            if (!_keyAuthorityDic.ContainsKey(newParentKey))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Parent authority doesn't exist exception
                return;
            }

            if (!_keyAuthorityDic.ContainsKey(authorityKey))
            {
                _syncLocker.ReleaseReaderLock();
                //TODO: Target key doesn't existed.
                return;
            }

            //Acquire a write lock.
            _syncLocker.UpgradeToWriterLock(Timeout.Infinite);

            int id;
            try
            {
                switch (Framework.Instance.Database.WorkingDatabaseType)
                {
                    case DatabaseType.MsSql:
                        id = PrivateMsSqlAddAuthority(authorityKey, _keyAuthorityDic[authorityKey]._title, _keyAuthorityDic[newParentKey]._id);
                        break;
                    default:
                        _syncLocker.ReleaseWriterLock();
                        //TODO: Throw unsupport exception.
                        return;
                }
            }
            catch
            {
                //Release locker.
                _syncLocker.ReleaseWriterLock();
                //Just throw the DatabaseOperationException.
                throw;
            }

            _keyAuthorityDic[authorityKey]._parent = _keyAuthorityDic[newParentKey]._id;

            _syncLocker.ReleaseWriterLock();
        }

        public Authority CreateSnapShot()
        {
            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            Authority root = null;
            try
            {
                root = PrivateCreateSnapShot();
            }
            finally
            {
                _syncLocker.ReleaseReaderLock();
            }

            return root;
        }

        public int GetParentId(int id)
        {
            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return _idAuthorityDic[id]._id;
            }
            finally
            {
                _syncLocker.ReleaseReaderLock();
            }
        }

        public int GetParentId(string key)
        {
            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return _keyAuthorityDic[key]._id;
            }
            finally
            {
                _syncLocker.ReleaseReaderLock();
            }
        }

        public string GetParentKey(int id)
        {
            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return _idAuthorityDic[id]._key;
            }
            finally
            {
                _syncLocker.ReleaseReaderLock();
            }
        }

        public string GetParentKey(string key)
        {
            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return _keyAuthorityDic[key]._key;
            }
            finally
            {
                _syncLocker.ReleaseReaderLock();
            }
        }

        public bool HasAuthority(string key)
        {
            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return _keyAuthorityDic.ContainsKey(key);
            }
            finally
            {
                _syncLocker.ReleaseReaderLock();
            }
        }

        public bool HasAuthority(int id)
        {
            _syncLocker.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return _idAuthorityDic.ContainsKey(id);
            }
            finally
            {
                _syncLocker.ReleaseReaderLock();
            }
        }


        #endregion

        #region Private Functions

        private Authority PrivateCreateSnapShot()
        {
            Authority root = null;
            Authority a;

            Dictionary<int, Authority> tempDic = new Dictionary<int, Core.Authority>();

            foreach(var pair in _idAuthorityDic)
            {
                a = new Authority(pair.Value._id, pair.Value._key, pair.Value._title, null);

                if (pair.Value._parent == -1)
                {
                    root = a;
                }
                else
                {
                    var p = tempDic[pair.Value._parent];
                    a.Parent = p;
                    p.Children.InternalAdd(a);
                }

                tempDic.Add(pair.Key, a);
            }

            tempDic.Clear();

            return root;
        }

        private void PrivateInitialize()
        {
            _syncLocker = new ReaderWriterLock();
            _syncLocker.AcquireWriterLock(Timeout.Infinite);

            _keyAuthorityDic = new Dictionary<string, AuthoritySlim>();
            _idAuthorityDic = new Dictionary<int, AuthoritySlim>();

            try
            {
                PrivateReadAuthoritiesFromDatabase();
            }
            finally
            {
                _syncLocker.ReleaseWriterLock();
            }
            
        }

        private void PrivateReadAuthoritiesFromDatabase()
        {
            using (var database = Framework.Instance.Database.GetDatabase())
            {
                var cmd = database.Connection.CreateCommand();
                cmd.CommandText = "SELECT * FROM [Authorities]";

                using (var reader = cmd.ExecuteReader())
                {
                    AuthoritySlim au;

                    while (reader.Read())
                    {
                        au = new Core.AuthoritySlim();
                        au._id = reader.GetInt32(0);
                        au._key = reader.GetString(1);
                        au._parent = reader.GetInt32(2);
                        au._title = reader.GetString(3);

                        _keyAuthorityDic.Add(au._key, au);
                        _idAuthorityDic.Add(au._id, au);
                    }

                    reader.Close();
                }

                cmd.Dispose();
            }
        }

        private int PrivateMsSqlAddAuthority(string key,string title,int parent)
        {
            Database database;
            int id = -1;

            try
            {
                database = Framework.Instance.Database.GetDatabase(DatabaseType.MsSql);
            }
            catch
            {
                //Just throw the exception.
                throw;
            }

            System.Data.SqlClient.SqlCommand cmd = database.Connection.CreateCommand() as System.Data.SqlClient.SqlCommand;
            if (cmd == null)
            {
                //The database is not the type that it has been defined by user.
                //TODO: Throw exception.
                return -1;
            }

            cmd.CommandText = "INSERT INTO [Authorities](`Key`,`Parent`,`Title`) VALUES (@key,@parent,@title)\nSELECT @@identity";
            cmd.Parameters.Add("key", System.Data.SqlDbType.NVarChar).Value = key;
            cmd.Parameters.Add("parent", System.Data.SqlDbType.Int).Value = parent;
            cmd.Parameters.Add("title", System.Data.SqlDbType.NVarChar).Value = title;

            try
            {
                id = Convert.ToInt32(cmd.ExecuteScalar());
            }
            catch
            {
                //Close database.
                database.Close();
                //Just throw the exception.
                throw;
            }

            return id;
        }

        private int PrivateMsSqlAlterAuthority(string key, string title, int parent)
        {
            Database database;
            int id = -1;

            try
            {
                database = Framework.Instance.Database.GetDatabase(DatabaseType.MsSql);
            }
            catch
            {
                //Just throw the exception.
                throw;
            }

            System.Data.SqlClient.SqlCommand cmd = database.Connection.CreateCommand() as System.Data.SqlClient.SqlCommand;
            if (cmd == null)
            {
                //The database is not the type that it has been defined by user.
                //TODO: Throw exception.
                return -1;
            }

            cmd.CommandText = "UPDATE [Authorities] SET `Parent`=@parent, `Title`=@title WHERE `Key`=@key";
            cmd.Parameters.Add("key", System.Data.SqlDbType.NVarChar).Value = key;
            cmd.Parameters.Add("parent", System.Data.SqlDbType.Int).Value = parent;
            cmd.Parameters.Add("title", System.Data.SqlDbType.NVarChar).Value = title;

            try
            {
                id = Convert.ToInt32(cmd.ExecuteScalar());
            }
            catch
            {
                //Close database.
                database.Close();
                //Just throw the exception.
                throw;
            }

            return id;
        }

        private int PrivateMsSqlAddAuthorityInBatch(Database database, string key, string title, int parent)
        {
            int id;

            System.Data.SqlClient.SqlCommand cmd = database.Connection.CreateCommand() as System.Data.SqlClient.SqlCommand;
            if (cmd == null)
            {
                //The database is not the type that it has been defined by user.
                //TODO: Throw exception.
                return -1;
            }

            cmd.CommandText = "INSERT INTO [Authorities](`Key`,`Parent`,`Title`) VALUES (@key,@parent,@title)\nSELECT @@identity";
            cmd.Parameters.Add("key", System.Data.SqlDbType.NVarChar).Value = key;
            cmd.Parameters.Add("parent", System.Data.SqlDbType.Int).Value = parent;
            cmd.Parameters.Add("title", System.Data.SqlDbType.NVarChar).Value = title;

            try
            {
                id = Convert.ToInt32(cmd.ExecuteScalar());
            }
            catch
            {
                //Just throw the exception.
                throw;
            }

            return id;
        }

        private void PrivateMsSqlUpdateAuthorityInBatch(Database database, string key, string title, int parent)
        {
            int id;

            System.Data.SqlClient.SqlCommand cmd = database.Connection.CreateCommand() as System.Data.SqlClient.SqlCommand;
            if (cmd == null)
            {
                //The database is not the type that it has been defined by user.
                //TODO: Throw exception.
                return;
            }

            cmd.CommandText = "UPDATE [Authorities] SET `Parent`=@parent, `Title`=@title WHERE `Key`=@key";
            cmd.Parameters.Add("key", System.Data.SqlDbType.NVarChar).Value = key;
            cmd.Parameters.Add("parent", System.Data.SqlDbType.Int).Value = parent;
            cmd.Parameters.Add("title", System.Data.SqlDbType.NVarChar).Value = title;

            try
            {
                cmd.ExecuteNonQuery();
            }
            catch
            {
                //Just throw the exception.
                throw;
            }

            return;
        }

        private void PrivateMsSqlConfigAuthorityBatch(string configText)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(configText);

            Database database;

            var root = xmlDoc.DocumentElement;
            if (root.Name != "Root" && Convert.ToSingle(root.Attributes["version"].Value) < 1.0f)
            {
                //TODO: Throw unsupported format or version exception.
                return;
            }

            database = Framework.Instance.Database.GetDatabase(DatabaseType.MsSql);

            XmlNode currentNode = xmlDoc.DocumentElement;
            try
            {
                PrivateMsSqlConfigAuthorityBatch(database, currentNode);
            }
            finally
            {
                database.Close();
            }
        }

        private void PrivateMsSqlConfigAuthorityBatch(Database database, XmlNode current)
        {
            if (current == null) return;
            if (current.NodeType != XmlNodeType.Element) return;

            string key = current.Attributes["key"].Value;
            string title = current.Attributes["title"].Value;
            string parentKey = current.ParentNode.Name == "Root" ? current.ParentNode.Attributes["basedKey"].Value : current.ParentNode.Attributes["key"].Value;
            bool rewrite = current.Attributes["rewrite"] != null ? current.Attributes["rewrite"].Value.ToLower() == "true" : false;

            if (_keyAuthorityDic.ContainsKey(key))
            {
                PrivateMsSqlUpdateAuthorityInBatch(database, key, title, _keyAuthorityDic[parentKey]._id);

                _keyAuthorityDic[key]._title = title;
                _keyAuthorityDic[key]._parent = _keyAuthorityDic[parentKey]._id;
                //TODO: Alter parent and title
            }
            else
            {
                int id = PrivateMsSqlAddAuthorityInBatch(database, key, title, _keyAuthorityDic[parentKey]._id);

                var auSlim = new AuthoritySlim();
                auSlim._id = id;
                auSlim._key = key;
                auSlim._parent = _keyAuthorityDic[parentKey]._id;
                auSlim._title = title;

                _idAuthorityDic.Add(id, auSlim);
                _keyAuthorityDic.Add(key, auSlim);
            }

            if (current.HasChildNodes)
                foreach (var child in current.ChildNodes)
                {
                    PrivateMsSqlConfigAuthorityBatch(database, child as XmlNode);
                }
            
        }

        #endregion

    }
}